import { MinusCircleOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { useCreation, useMemoizedFn } from 'ahooks';
import { Button, Flex, InputNumber, Radio, Select, Tabs, Typography, Tooltip, App } from 'antd';
import { t } from 'i18next';
import React, { useCallback, useEffect, useMemo, useState, useTransition } from 'react';
import { NodeProps } from 'reactflow';
import { useContextSelector } from 'use-context-selector';

import CollapsibleSection from '@/components/CollapsibleSection';
import HttpInput from '@/components/common/Input/HttpInput';
import JsonEditor from '@/components/Textarea/JsonEditor';
import PromptEditor from '@/components/Textarea/PromptEditor';
import { AppContext } from '@/pages/app/detail/context';
import { EditorVariableLabelPickerType } from '@/types/workflow/components/common/Textarea/PromptEditor/type';
import {
  ContentTypes,
  FlowNodeInputTypeEnum,
  NodeInputKeyEnum,
  WorkflowIOValueTypeEnum,
} from '@/types/workflow/constant';
import { FlowNodeInputItemType } from '@/types/workflow/io';
import { FlowNodeItemType } from '@/types/workflow/node';

import { WorkflowContext } from '../../../context';
import { getEditorVariables } from '../../../utils';
import NodeCard from '../render/NodeCard';
import RenderInput from '../render/RenderInput';
import RenderOutput from '../render/RenderOutput';
import RenderToolInput from '../render/RenderToolInput';

import CurlImportModal from './CurlImportModal';

const { Text } = Typography;

const defaultFormBody = {
  key: NodeInputKeyEnum.httpFormBody,
  renderTypeList: [FlowNodeInputTypeEnum.hidden],
  valueType: WorkflowIOValueTypeEnum.any,
  value: [],
  label: '',
  required: false,
};

enum TabEnum {
  params = 'params',
  headers = 'headers',
  body = 'body',
}
export type PropsArrType = {
  key: string;
  type: string;
  value: string;
};

const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
  nodeId,
  inputs,
}: {
  nodeId: string;
  inputs: FlowNodeInputItemType[];
}) {
  const { message } = App.useApp();
  const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
  const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
  const edges = useContextSelector(WorkflowContext, (v) => v.edges);
  const { appDetail } = useContextSelector(AppContext, (v) => v);
  const [isOpen, setIsOpen] = useState(false);

  const onOpenCurl = () => {
    setIsOpen(true);
  };

  const onCloseCurl = () => {
    setIsOpen(false);
  };

  const requestMethods = inputs.find(
    (item) => item.key === NodeInputKeyEnum.httpMethod,
  ) as FlowNodeInputItemType;
  const requestUrl = inputs.find(
    (item) => item.key === NodeInputKeyEnum.httpReqUrl,
  ) as FlowNodeInputItemType;

  const onChangeUrl = (value: string) => {
    onChangeNode({
      nodeId,
      type: 'updateInput',
      key: NodeInputKeyEnum.httpReqUrl,
      value: {
        ...requestUrl,
        value,
      },
    });
  };
  const onBlurUrl = (val: string) => {
    // 拆分params和url
    const url = val.split('?')[0];
    const params = val.split('?')[1];
    if (params) {
      const paramsArr = params.split('&');
      const paramsObj = paramsArr.reduce((acc, cur) => {
        const [key, value] = cur.split('=');
        return {
          ...acc,
          [key]: value,
        };
      }, {});
      const inputParams = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams);

      if (!inputParams || Object.keys(paramsObj).length === 0) return;

      const concatParams: PropsArrType[] = inputParams?.value || [];
      Object.entries(paramsObj).forEach(([key, value]) => {
        if (!concatParams.find((item) => item.key === key)) {
          concatParams.push({ key, value: value as string, type: 'string' });
        }
      });

      onChangeNode({
        nodeId,
        type: 'updateInput',
        key: NodeInputKeyEnum.httpParams,
        value: {
          ...inputParams,
          value: concatParams,
        },
      });

      onChangeNode({
        nodeId,
        type: 'updateInput',
        key: NodeInputKeyEnum.httpReqUrl,
        value: {
          ...requestUrl,
          value: url,
        },
      });

      message.success(t('core.module.http.Url and params have been split'));
    }
  };

  const variables = useCreation(() => {
    return getEditorVariables({
      nodeId,
      nodeList,
      edges,
      appDetail,
      t,
    });
  }, [nodeId, nodeList, edges, appDetail, t]);

  return (
    <div>
      <div className="mb-8 flex items-center justify-between">
        <Text strong style={{ color: '#666' }}>
          {t('core.module.Http request settings')}
        </Text>
        <Button type="link" onClick={onOpenCurl}>
          {t('core.module.http.curl import')}
        </Button>
      </div>
      <div className="nodrag flex items-center">
        <Select
          popupClassName="workflow-dropdown"
          style={{ width: 88 }}
          value={requestMethods?.value}
          options={[
            { label: 'GET', value: 'GET' },
            { label: 'POST', value: 'POST' },
            { label: 'PUT', value: 'PUT' },
            { label: 'DELETE', value: 'DELETE' },
            { label: 'PATCH', value: 'PATCH' },
          ]}
          onChange={(e) => {
            onChangeNode({
              nodeId,
              type: 'updateInput',
              key: NodeInputKeyEnum.httpMethod,
              value: {
                ...requestMethods,
                value: e,
              },
            });
          }}
        />
        <div
          style={{
            width: '100%',
            marginLeft: 8,
            border: 'none',
            borderRadius: '20px',
            backgroundColor: 'transparent',
          }}
        >
          <PromptEditor
            placeholder={`${t('core.module.input.label.Http Request Url')}, ${t(
              'textarea_variable_picker_tip',
            )}`}
            tip=""
            value={requestUrl?.value || ''}
            variableLabels={variables}
            variables={variables}
            onBlur={onBlurUrl}
            onChange={onChangeUrl}
            minH={40}
            showOpenModal={false}
          />
        </div>
      </div>

      {isOpen && <CurlImportModal nodeId={nodeId} inputs={inputs} onClose={onCloseCurl} />}
    </div>
  );
});

export function RenderHttpProps({
  nodeId,
  inputs,
}: {
  nodeId: string;
  inputs: FlowNodeInputItemType[];
}) {
  const [selectedTab, setSelectedTab] = useState(TabEnum.params);
  const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
  const edges = useContextSelector(WorkflowContext, (v) => v.edges);

  const { appDetail } = useContextSelector(AppContext, (v) => v);

  const requestMethods = inputs.find((item) => item.key === NodeInputKeyEnum.httpMethod)?.value;
  const params = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams);
  const headers = inputs.find((item) => item.key === NodeInputKeyEnum.httpHeaders);
  const jsonBody = inputs.find((item) => item.key === NodeInputKeyEnum.httpJsonBody);
  const formBody =
    inputs.find((item) => item.key === NodeInputKeyEnum.httpFormBody) || defaultFormBody;
  const contentType = inputs.find((item) => item.key === NodeInputKeyEnum.httpContentType);

  const paramsLength = params?.value?.length || 0;
  const headersLength = headers?.value?.length || 0;

  // get variable
  const variables = useCreation(() => {
    return getEditorVariables({
      nodeId,
      nodeList,
      edges,
      appDetail,
      t,
    });
  }, [nodeId, nodeList, edges, appDetail, t]);

  const variableText = useMemo(() => {
    return variables
      .map((item) => `${item.key}${item.key !== item.label ? `(${item.label})` : ''}`)
      .join('\n');
  }, [variables]);

  const stringifyVariables = useMemo(
    () =>
      JSON.stringify({
        params,
        headers,
        jsonBody,
        variables,
      }),
    [headers, jsonBody, params, variables],
  );

  const Render = useMemo(() => {
    const { params, headers, jsonBody, variables } = JSON.parse(stringifyVariables);
    return (
      <div>
        <Flex align="center" style={{ marginBottom: 8 }}>
          <Text strong style={{ color: '#666' }}>
            {t('core.module.Http request props')}
          </Text>
          <Tooltip title={t('core.module.http.Props tip', { variable: variableText })}>
            <QuestionCircleOutlined style={{ marginLeft: 4 }} />
          </Tooltip>
        </Flex>
        <Tabs
          activeKey={selectedTab}
          onChange={(key) => setSelectedTab(key as TabEnum)}
          items={[
            {
              key: TabEnum.params,
              label: <RenderPropsItem text="Params" num={paramsLength} />,
            },
            ...(!['GET', 'DELETE'].includes(requestMethods)
              ? [
                  {
                    key: TabEnum.body,
                    label: (
                      <Flex align="center">
                        Body
                        {(jsonBody?.value || !!formBody?.value?.length) &&
                          contentType?.value !== ContentTypes.none && (
                            <span style={{ marginLeft: 4 }}>✅</span>
                          )}
                      </Flex>
                    ),
                  },
                ]
              : []),
            {
              key: TabEnum.headers,
              label: <RenderPropsItem text="Headers" num={headersLength} />,
            },
          ]}
          style={{ minWidth: '500px', marginBottom: 8 }}
        />
        <div style={{ minWidth: '500px' }}>
          {params &&
            headers &&
            jsonBody &&
            {
              [TabEnum.params]: <RenderForm nodeId={nodeId} input={params} variables={variables} />,
              [TabEnum.body]: (
                <RenderBody
                  nodeId={nodeId}
                  variables={variables}
                  jsonBody={jsonBody}
                  formBody={formBody}
                  typeInput={contentType}
                />
              ),
              [TabEnum.headers]: (
                <RenderForm nodeId={nodeId} input={headers} variables={variables} />
              ),
            }[selectedTab]}
        </div>
      </div>
    );
  }, [
    contentType,
    formBody,
    headersLength,
    nodeId,
    paramsLength,
    requestMethods,
    selectedTab,
    stringifyVariables,
    variableText,
  ]);

  return Render;
}
function RenderHttpTimeout({
  nodeId,
  inputs,
}: {
  nodeId: string;
  inputs: FlowNodeInputItemType[];
}) {
  const timeout = inputs.find((item) => item.key === NodeInputKeyEnum.httpTimeout)!;
  const [isEditTimeout, setIsEditTimeout] = useState(false);
  const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);

  return (
    <Flex justify="space-between" align="center">
      <Text strong style={{ color: '#666' }}>
        {t('core.module.Http timeout')}
      </Text>
      <div>
        {isEditTimeout ? (
          <InputNumber
            defaultValue={timeout.value}
            min={timeout.min}
            max={timeout.max}
            style={{ backgroundColor: 'white' }}
            onBlur={() => setIsEditTimeout(false)}
            onChange={(e) => {
              onChangeNode({
                nodeId,
                type: 'updateInput',
                key: NodeInputKeyEnum.httpTimeout,
                value: {
                  ...timeout,
                  value: Number(e),
                },
              });
            }}
            autoFocus
          />
        ) : (
          <Button
            style={{ color: '#666' }}
            onClick={() => setIsEditTimeout(true)}
          >{`${timeout?.value} s`}</Button>
        )}
      </div>
    </Flex>
  );
}
const RenderForm = ({
  nodeId,
  input,
  variables,
}: {
  nodeId: string;
  input: FlowNodeInputItemType;
  variables: EditorVariableLabelPickerType[];
}) => {
  const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
  const { message } = App.useApp();
  const [list, setList] = useState<PropsArrType[]>(input.value || []);
  const [updateTrigger, setUpdateTrigger] = useState(false);
  const [shouldUpdateNode, setShouldUpdateNode] = useState(false);

  useEffect(() => {
    setList(input.value || []);
  }, [input.value]);

  useEffect(() => {
    if (shouldUpdateNode) {
      onChangeNode({
        nodeId,
        type: 'updateInput',
        key: input.key,
        value: {
          ...input,
          value: list,
        },
      });
      setShouldUpdateNode(false);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [list]);

  const handleKeyChange = useCallback(
    (index: number, newKey: string) => {
      setList((prevList) => {
        if (!newKey) {
          setUpdateTrigger((prev) => !prev);
        } else if (prevList.find((item, i) => i !== index && item.key === newKey)) {
          setUpdateTrigger((prev) => !prev);
          message.warning(t('core.module.http.Key already exists'));
        }
        return prevList.map((item, i) => (i === index ? { ...item, key: newKey } : item));
      });
      setShouldUpdateNode(true);
    },
    [message],
  );

  // Add new params/headers key
  const handleAddNewProps = useCallback(
    (value: string) => {
      setList((prevList) => {
        if (!value) {
          return prevList;
        }

        const checkExist = prevList.find((item) => item.key === value);
        if (checkExist) {
          setUpdateTrigger((prev) => !prev);
          message.warning(t('core.module.http.Key already exists'));
          return prevList;
        }
        return [...prevList, { key: value, type: 'string', value: '' }];
      });

      setShouldUpdateNode(true);
    },
    [message],
  );

  const Render = useMemo(() => {
    return (
      <div
        style={{
          borderRadius: '4px',
          overflow: 'hidden',
          border: '1px solid #e2e8f0',
          backgroundColor: 'white',
        }}
      >
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr>
              <th
                style={{
                  padding: '8px',
                  borderBottom: '1px solid #e2e8f0',
                  textAlign: 'left',
                }}
              >
                {t('core.module.http.Props name')}
              </th>
              <th
                style={{
                  padding: '8px',
                  borderBottom: '1px solid #e2e8f0',
                  textAlign: 'left',
                }}
              >
                {t('core.module.http.Props value')}
              </th>
            </tr>
          </thead>
          <tbody>
            {[...list, { key: '', value: '', type: '' }].map((item, index) => (
              <tr key={`${input.key}${index}`}>
                <td
                  style={{
                    padding: 0,
                    width: '50%',
                    borderRight: '1px solid #e2e8f0',
                  }}
                >
                  <HttpInput
                    placeholder={t('textarea_variable_picker_tip')}
                    value={item.key}
                    variableLabels={variables}
                    variables={variables}
                    h={20}
                    onBlur={(val) => {
                      handleKeyChange(index, val);

                      // Last item blur, add the next item.
                      if (index === list.length && val) {
                        handleAddNewProps(val);
                        setUpdateTrigger((prev) => !prev);
                      }
                    }}
                    updateTrigger={updateTrigger}
                  />
                </td>
                <td style={{ padding: 0, width: '50%' }}>
                  <div className="flex h-full items-center">
                    <HttpInput
                      placeholder={t('textarea_variable_picker_tip')}
                      value={item.value}
                      variables={variables}
                      variableLabels={variables}
                      h={20}
                      onBlur={(val) => {
                        setList((prevList) =>
                          prevList.map((item, i) => (i === index ? { ...item, value: val } : item)),
                        );
                        setShouldUpdateNode(true);
                      }}
                    />
                    {index !== list.length && (
                      <Button
                        style={{
                          cursor: 'pointer',
                          width: '14px',
                          marginRight: '8px',
                        }}
                        className="ml-auto"
                        type="text"
                        icon={<MinusCircleOutlined />}
                        onClick={() => {
                          setList((prevlist) => prevlist.filter((val) => val.key !== item.key));
                          setShouldUpdateNode(true);
                        }}
                      />
                    )}
                  </div>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }, [handleAddNewProps, handleKeyChange, input.key, list, updateTrigger, variables]);

  return Render;
};
const RenderBody = ({
  nodeId,
  jsonBody,
  formBody,
  typeInput,
  variables,
}: {
  nodeId: string;
  jsonBody: FlowNodeInputItemType;
  formBody: FlowNodeInputItemType;
  typeInput: FlowNodeInputItemType | undefined;
  variables: EditorVariableLabelPickerType[];
}) => {
  const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
  const [_, startSts] = useTransition();

  useEffect(() => {
    if (typeInput === undefined) {
      onChangeNode({
        nodeId,
        type: 'addInput',
        value: {
          key: NodeInputKeyEnum.httpContentType,
          renderTypeList: [FlowNodeInputTypeEnum.hidden],
          valueType: WorkflowIOValueTypeEnum.string,
          value: ContentTypes.json,
          label: '',
          required: false,
        },
      });
    }
  }, [nodeId, onChangeNode, typeInput]);

  const Render = useMemo(() => {
    return (
      <div>
        <Radio.Group
          value={typeInput?.value}
          onChange={(e) => {
            onChangeNode({
              nodeId,
              type: 'updateInput',
              key: NodeInputKeyEnum.httpContentType,
              value: {
                key: NodeInputKeyEnum.httpContentType,
                renderTypeList: [FlowNodeInputTypeEnum.hidden],
                valueType: WorkflowIOValueTypeEnum.string,
                value: e.target.value,
                label: '',
                required: false,
              },
            });
          }}
          buttonStyle="solid"
          style={{
            marginBottom: 8,
            display: 'flex',
            flexWrap: 'wrap',
            gap: 8,
          }}
        >
          {Object.values(ContentTypes).map((item) => (
            <Radio.Button
              key={item}
              value={item}
              style={{
                whiteSpace: 'nowrap',
              }}
            >
              {item}
            </Radio.Button>
          ))}
        </Radio.Group>
        {(typeInput?.value === ContentTypes.formData ||
          typeInput?.value === ContentTypes.xWwwFormUrlencoded) && (
          <RenderForm nodeId={nodeId} input={formBody} variables={variables} />
        )}
        {typeInput?.value === ContentTypes.json && (
          <JsonEditor
            defaultHeight={200}
            resize
            value={jsonBody.value}
            placeholder={t('core.module.template.http body placeholder')}
            onChange={(e) => {
              startSts(() => {
                onChangeNode({
                  nodeId,
                  type: 'updateInput',
                  key: jsonBody.key,
                  value: {
                    ...jsonBody,
                    value: e,
                  },
                });
              });
            }}
            variables={variables}
          />
        )}
        {(typeInput?.value === ContentTypes.xml || typeInput?.value === ContentTypes.raw) && (
          <PromptEditor
            value={jsonBody.value}
            placeholder={t('textarea_variable_picker_tip')}
            onChange={(e) => {
              startSts(() => {
                onChangeNode({
                  nodeId,
                  type: 'updateInput',
                  key: jsonBody.key,
                  value: {
                    ...jsonBody,
                    value: e,
                  },
                });
              });
            }}
            showOpenModal={false}
            variableLabels={variables}
            minH={200}
          />
        )}
      </div>
    );
  }, [typeInput?.value, nodeId, formBody, variables, jsonBody, onChangeNode]);
  return Render;
};

function RenderPropsItem({ text, num }: { text: string; num: number }) {
  return (
    <Flex align="center">
      <span>{text}</span>
      {num > 0 && (
        <span
          style={{
            marginLeft: 4,
            borderRadius: '50%',
            backgroundColor: '#edf2f7',
            padding: '0 8px',
          }}
        >
          {num}
        </span>
      )}
    </Flex>
  );
}

function NodeHttp({ data, selected }: NodeProps<FlowNodeItemType>) {
  const { nodeId, inputs, outputs } = data;
  const splitToolInputs = useContextSelector(WorkflowContext, (v) => v.splitToolInputs);
  const { commonInputs, isTool } = splitToolInputs(inputs, nodeId);

  const [expandedSections, setExpandedSections] = useState<Set<'output' | 'input' | 'params'>>(
    new Set(['input']),
  );

  const toggleSection = (section: 'output' | 'input' | 'params') => {
    setExpandedSections((prev) => {
      const newSet = new Set(prev);
      if (newSet.has(section)) {
        newSet.delete(section);
      } else {
        newSet.add(section);
      }
      return newSet;
    });
  };

  const HttpMethodAndUrl = useMemoizedFn(() => (
    <RenderHttpMethodAndUrl nodeId={nodeId} inputs={inputs} />
  ));
  const Headers = useMemoizedFn(() => <RenderHttpProps nodeId={nodeId} inputs={inputs} />);
  const HttpTimeout = useMemoizedFn(() => <RenderHttpTimeout nodeId={nodeId} inputs={inputs} />);

  const CustomComponents = useMemo(() => {
    return {
      [NodeInputKeyEnum.httpMethod]: HttpMethodAndUrl,
      [NodeInputKeyEnum.httpHeaders]: Headers,
      [NodeInputKeyEnum.httpTimeout]: HttpTimeout,
    };
  }, [Headers, HttpMethodAndUrl, HttpTimeout]);

  return (
    <NodeCard minW="350px" selected={selected} {...data}>
      <div className="flex flex-col gap-3 space-y-0">
        <CollapsibleSection
          title="请求配置"
          subtitle="URL、方法、请求参数、请求体和请求头配置"
          isExpanded={expandedSections.has('input')}
          onToggle={() => toggleSection('input')}
          type="input"
        >
          <div className="space-y-2">
            {isTool && <RenderToolInput nodeId={nodeId} inputs={inputs} />}
          </div>
          <RenderInput
            nodeId={nodeId}
            flowInputList={commonInputs}
            CustomComponent={CustomComponents}
          />
        </CollapsibleSection>

        <CollapsibleSection
          title={t('workflow.node.output')}
          subtitle={t('workflow.node.output_desc')}
          isExpanded={expandedSections.has('output')}
          onToggle={() => toggleSection('output')}
          type="output"
          className="pb-3"
        >
          <div className="px-2">
            <RenderOutput flowOutputList={outputs} nodeId={nodeId} />
          </div>
        </CollapsibleSection>
      </div>
    </NodeCard>
  );
}
export default React.memo(NodeHttp);
